#!/usr/bin/env python
#
# For this to work, you need to have the main directory that contains
# parser/ and semantics/ in your PYTHONPATH.
#

import os, sys
# print os.environ['PYTHONPATH']

from decompile import lambdastr
from nltk.tree import Tree
from Eparser.feature import *
from Eparser import parser

trace = 3

TITLE = "6.863 Semantic Framework"
VERSION = "1.1"
DATE = "April 14, 2004"

class MutableNode(object):
	def __init__(self, value):
		self.value = value
	def setval(self, newval):
		self.value = newval
	def __str__(self):
		if isinstance(self.value, str): return self.value
		else: return repr(self)
	def __repr__(self):
		return lambdastr(self.value)

class TreeLog(object):
	def __init__(self):
		self.trees = []
	def add(self, tree):
		self.trees.append(tree)

class SemanticRuleSet(object):
	def __init__(self):
		self._dict = {}
		self._grammar = []
		self.learned = SemanticDatabase()
	def addMatch(self, production, func):
		if isinstance(production, str):
			production = parseRule(production)
		self._dict[production] = func
	def add(self, production, func):
		if isinstance(production, str):
			production = parseRule(production)
		self.addMatch(production, func)
		self._grammar.append(production)
	def get(self, production):
		if isinstance(production, str):
			production = parseRule(production)
		return self._dict.get(production)
	def addLexicon(self, preterminal, terminals):
		if isinstance(preterminal, str):
			preterminal = Category.parse(preterminal)
		for terminal in terminals:
			prod = CFGProduction(preterminal, terminal)
			self._grammar.append(prod)
	def items(self): return self._dict.items()
	def grammar(self):
		return parser.EarleyCFG(C("Start"), self._grammar)

	def _print(self):
		print self._grammar
		print self._dict
		print self.learned

def sql_quote(s):
	if (str(s) == '-'):
		return "NULL"
	else:
		return "'" + str(s) + "'"

def sql_where_clauses(lst):
	fst = True
	rtn = ""
	for i in range(len(lst)):
		name = lst[i][0]
		value = lst[i][1]
		if str(value) != '-':
			if not(fst):
				rtn += " AND "
			else:
				fst = False
			rtn += name + "=" + sql_quote(value)
	return(rtn)

def features_and_variables(lst):
	feats = []
	vars = []
	for i in range(len(lst)):
		name = lst[i][0]
		value = lst[i][1]
		# print (name,value)
		if str(value)[0] == '?':
			vars.append([name,value.varname()])
		else:
			feats.append([name,value.getFeature("name").value()])
	return [feats, vars]

class SemanticDatabase(object):
	def __init__(self):
		self._facts = []
	def add(self, fact):
		table = fact.symbol()
		action = sql_quote(fact.getFeature("action").value())
		agent = sql_quote(fact.getFeature("agent").value().getFeature("name").value())
		patient = sql_quote(fact.getFeature("patient").value().getFeature("name").value())
		beneficiary = sql_quote(fact.getFeature("beneficiary").value().getFeature("name").value())
		tense = sql_quote(fact.getFeature("tense").value())
		print "fact"
		print "INSERT INTO", table, "VALUES (", action, ",", agent, ",", patient, ",", beneficiary, ",", tense, ")"
		self._facts.insert(0, fact)
	def match(self, fact):
		table = fact.symbol()
		action = fact.getFeature("action").value()
		agent = fact.getFeature("agent").value().getFeature("name").value()
		patient = fact.getFeature("patient").value().getFeature("name").value()
		beneficiary = fact.getFeature("beneficiary").value().getFeature("name").value()
		tense = fact.getFeature("tense").value()
		print "ynQuestion"
		print "SELECT CASE WHEN COUNT(*) > 0 THEN 'Yes' else 'No' END FROM", table, "WHERE",
		print sql_where_clauses([["action",action],["agent",agent],["patient",patient],["beneficiary",beneficiary],["tense",tense]])
		for fact2 in self._facts:
			if fact2.match(fact) is not None: return fact2
		return None
	def matchVar(self, fact):
		table = fact.symbol()
		fv = features_and_variables([[x,fact.getFeature(x).value()] for x in ["agent","patient","beneficiary"]])
		feats = fv[0]
		vars = fv[1]
		feats = [["action",fact.getFeature("action").value()],["tense",fact.getFeature("tense").value()]] + feats
		print "whQuestion"
		print "SELECT DISTINCT", (",".join([x[0] for x in vars])), "FROM", table, "WHERE", (sql_where_clauses(feats))
		for fact2 in self._facts:
			vars = fact2.match(fact)
			if vars is not None:
				return vars
		return None
		
def nodevalue(node):
	if not isinstance(node, Tree): return node.value
	else: return node.node().value

def mutablize(node):
	if not isinstance(node, Tree): return MutableNode(node)
	return Tree(MutableNode(node.node()), *[mutablize(c) for c in\
		node.children()])

def stringify(node):
	if not isinstance(node, Tree): return str(node)
	return Tree(str(node.node()).replace(": ",":\n").replace("@","\n@"),
		*[stringify(c) for c in node.children()])

def process_tree(node, semantic_dict, treelog=None):
	tree = mutablize(node)
	return recurse_tree(tree, tree, semantic_dict, treelog)

def recurse_tree(node, orig, semantic_dict, treelog=None):
	if not isinstance(node, Tree):
		return node.value
	root = nodevalue(node)
	children = [nodevalue(x) for x in node.children()]
	children_str = " ".join([repr(x) for x in children])
	if children_str == '-':
		children = []
	edge_str = "%s -> %s" % (root, children_str)
	loc_str = " ".join([str(x) for x in node.leaves()])
	for (edge, expr) in semantic_dict.items():
		if expr is None: continue
		# Find the first entry in the semantic table for this edge
		lhsvars = root.match(edge.lhs())
		if lhsvars is not None:
			rhsvars = matchall(children, edge.rhs())
			vars = merge(lhsvars, rhsvars)
			# print "vars:"
			# print vars
			if vars is not None:
				node.node().setval(expr)
				if treelog is not None: treelog.add(stringify(orig))
				if trace > 1:
					print ">> Evaluating: %s" % edge_str
					print "         Text: %s" % loc_str
					print "   Expression: %s" % lambdastr(expr)
					print
					
				if children == []: args = []
				else:
					args = [recurse_tree(subtree, orig, semantic_dict, treelog)\
						for subtree	in node.children()]
				
				try:
					result = apply(expr, args)
				except TypeError:
					print "Could not apply %s to %s" % (lambdastr(expr), args)
					raise
				
				if trace:
					print "<< Evaluated: %s" % edge_str
					print "        Text: %s" % loc_str
					print "       Value: %s" % (lambdastr(result))
					print
				# print result
				node.node().setval(result)
				if treelog is not None: treelog.add(stringify(orig))
				return result
	
	raise ValueError, "Could not find a semantic entry for %s -> %s" %\
	(root, children_str)

def matchall(tok1, tok2):
	if len(tok1) != len(tok2): return None
	vars = {}
	for (t1, t2) in zip(tok1, tok2):
		if type(t1) == str:
			if type(t2) == str and t1.upper() != t2.upper(): return None
		else:
			vars = merge(vars, t1.match(t2))
			if vars is None: return None
	return vars

def demo_sentence(sentence):
	import lab_rules
	parseit = parser.ParserInterface(lab_rules.sem.grammar(), trace=trace)
	tree = parseit.getOneParse(sentence)
	if tree is None: print "The sentence did not parse."
	else:
		tree = tree.type()
		# print tree
		print process_tree(tree, lab_rules.sem)

sqlInit = False

def sql_demo_sentence(dbc,sentence):
	import lab_rules
	# print "bbbb"
	# lab_rules.sem._print()
	# print "aaaa"
	global trace
	global sqlInit
	if not(sqlInit):
		lab_rules.initSQL(dbc)
	lab_rules.loadVocab()
	trace = 0
	parseit = parser.ParserInterface(lab_rules.sem.grammar(), trace=trace)
	tree = parseit.getOneParse(sentence)
	if tree is None: 
		print "NO PARSES"
		print trace
	else:
		tree = tree.type()
		# print tree
		# print "starting process tree"
		a = process_tree(tree, lab_rules.sem)
		# print "tree return:" 
		# print a
		return a

def sql_list_objects(dbc):
	import lab_rules
	global sqlInit	
	if not (sqlInit):
		lab_rules.initSQL(dbc)
	return lab_rules.sql.listObjects()


def demo():
	import sys, lab_rules
	while True:
		sys.stdout.write("> ")
		sentence = sys.stdin.readline()
		if not sentence: break
		else:
			parseit = parser.ParserInterface(lab_rules.sem.grammar(), trace=trace)
			tree = parseit.getOneParse(sentence)
			if tree is None: print "I don't understand."
			else:
				tree = tree.type()
				print process_tree(tree, lab_rules.sem)

def main():
	global trace
	import sys, os, shutil
	from optparse import OptionParser, OptionGroup
	usage = """%%prog [options] [rule_file]

%(TITLE)s
version %(VERSION)s (%(DATE)s)
by Rob Speer
Distributed under the GPL. See LICENSE.TXT for information.""" % globals()

	homedir = os.path.expanduser('~')
	newdir = os.sep.join([homedir, ".6863semantics"])
	if not os.path.isdir(newdir):
		sys.stderr.write(\
"""The semantics engine writes its working file to a directory under your
home directory, ~/.6863semantics, instead of to /tmp. That directory will be
created now.
""")
		os.mkdir(newdir)
	tmpdir = newdir
	sys.path.insert(0, tmpdir)
	curfile = os.sep.join([tmpdir, "current_rules.py"])
	opts = OptionParser(usage=usage)
	opts.add_option("-b", "--batch", metavar="FILE", dest="batchfile",
	default=None, help="Batch test: interpret all the lines in a file")
	opts.add_option("-d", "--draw", action="store_true", dest="draw",
	help="show lambda applications step-by-step in a GUI")
	opts.add_option("-v", "--verbose", action="store_true", dest="verbose",
	help="output lots of lambda applications [default]")
	opts.add_option("-q", "--quiet", action="store_true", dest="quiet",
	help="output only the responses [default in batch mode]")
	
	(options, args) = opts.parse_args()
	trace = 2
	batch = False
	if options.batchfile is not None:
		trace = 0
		batch = True
	if options.verbose: trace = 2
	if options.quiet: trace = 0
	if not len(args):
		opts.print_help()
		sys.exit(0)
	infile = args[0]
	
	if not os.access(infile, os.R_OK):
		sys.stderr.write("Error: Could not open %s.\n" % infile)
		sys.exit(0)
	try:
		shutil.copy(infile, curfile)
	except:
		sys.stderr.write("Error: Could not write to temp directory (%s).\n" % tmpdir)
		sys.exit(0)
	
	if trace: print "Loading semantics..."
	import current_rules
	print
	print "Hello."
	instream = None
	if batch:
		try:
			instream = open(options.batchfile)
		except:
			sys.stderr.write("Error: Could not open batch file: %s\n" % options.batchfile)
			sys.exit(0)
	else:
		instream = sys.stdin
	while True:
		sys.stdout.write("> ")
		sentence = instream.readline()
		if (not sentence) or (len(sentence) <= 1): break
		else:
			parseit = parser.ParserInterface(current_rules.sem.grammar(), trace=trace)
			tree = parseit.getOneParse(sentence)
			if tree is None: print "I don't understand."
			else:
				tree = tree.type()
				treelog = TreeLog()
				print process_tree(tree, current_rules.sem, treelog)
				if options.draw:
					import drawtree
					drawtree.TreeView(treelog.trees)

if __name__ == '__main__':
	main()


